/*
 * Copyright (c) 2024 Huawei Technologies Co., Ltd.
 * openFuyao is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

package consoleplugin

import (
	"fmt"
	"reflect"
	"testing"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"
	dynamicfake "k8s.io/client-go/dynamic/fake"
	"k8s.io/client-go/rest"
)

var testConsolePluginUnstructured = unstructured.Unstructured{
	Object: map[string]interface{}{
		"apiVersion": "console.openfuyao.com/v1beta1",
		"kind":       "ConsolePlugin",
		"metadata": map[string]interface{}{
			"name": "test-consoleplugin",
		},
		"spec": map[string]interface{}{
			"pluginName":  "test-consoleplugin",
			"displayName": "测试扩展",
			"entrypoint":  "/",
			"backend": map[string]interface{}{
				"type": "Service",
				"service": map[string]interface{}{
					"name":      "test-consoleplugin",
					"namespace": "test-consoleplugin-ns",
				},
			},
			"enabled": true,
		},
	},
}

var testConsolePlugin = ConsolePlugin{
	TypeMeta: metav1.TypeMeta{
		APIVersion: "console.openfuyao.com/v1beta1",

		Kind: "ConsolePlugin",
	},
	ObjectMeta: metav1.ObjectMeta{
		Name: "test-consoleplugin",
	},
	Spec: ConsolePluginSpec{
		PluginName:  "test-consoleplugin",
		DisplayName: "测试扩展",
		Entrypoint:  "/",
		Backend: &ConsolePluginBackend{
			Type: ServiceBackendType,
			Service: &ConsolePluginService{
				Name:      "test-consoleplugin",
				Namespace: "test-consoleplugin-ns",
			},
		},
		Enabled: true,
	},
}

var dummyConsolePluginUnstructured = unstructured.Unstructured{
	Object: map[string]interface{}{
		"apiVersion": "console.openfuyao.com/v1beta1",
		"kind":       "ConsolePlugin",
		"metadata": map[string]interface{}{
			"name": "dummy-consoleplugin",
		},
		"spec": map[string]interface{}{
			"pluginName":  "dummy-consoleplugin",
			"displayName": "Dummy Plugin",
			"entrypoint":  "/container_platform",
			"backend": map[string]interface{}{
				"type": "Service",
				"service": map[string]interface{}{
					"name":      "test-consoleplugin",
					"namespace": "test-consoleplugin-ns",
				},
			},
			"enabled": false,
		},
	},
}

var dummyConsolePlugin = ConsolePlugin{
	TypeMeta: metav1.TypeMeta{
		APIVersion: "console.openfuyao.com/v1beta1",
		Kind:       "ConsolePlugin",
	},
	ObjectMeta: metav1.ObjectMeta{
		Name: "dummy-consoleplugin",
	},
	Spec: ConsolePluginSpec{
		PluginName:  "dummy-consoleplugin",
		DisplayName: "Dummy Plugin",
		Entrypoint:  "/container_platform",
		Backend: &ConsolePluginBackend{
			Type: ServiceBackendType,
			Service: &ConsolePluginService{
				Name:      "test-consoleplugin",
				Namespace: "test-consoleplugin-ns",
			},
		},
		Enabled: false,
	},
}

func newFakeDynamicClientSet() *dynamicfake.FakeDynamicClient {
	scheme := runtime.NewScheme()
	return dynamicfake.NewSimpleDynamicClientWithCustomListKinds(
		scheme,
		map[schema.GroupVersionResource]string{
			{Group: "console.openfuyao.com", Version: "v1beta1", Resource: "consoleplugins"}: "ConsolePluginList",
		},
		&testConsolePluginUnstructured,
		&dummyConsolePluginUnstructured,
	)
}

func newTestPluginManager() *ConsolePluginManager {
	return &ConsolePluginManager{
		Client: newFakeDynamicClientSet(),
	}
}

func TestConsolePluginManagerCheckPluginEnablementIfInstalled(t *testing.T) {
	manager := newTestPluginManager()
	tests := []struct {
		name       string
		manager    *ConsolePluginManager
		pluginName string
		want       bool
		wantErr    bool
	}{
		{
			"TestUninstalledConsolePlugin",
			manager,
			"non-installed-consoleplugin",
			false,
			true,
		},
		{
			"TestInstalledDisabledConsolePlugin",
			manager,
			"test-consoleplugin",
			true,
			false,
		},
		{
			"TestInstalledEnabledConsolePlugin",
			manager,
			"dummy-consoleplugin",
			false,
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := tt.manager.CheckPluginEnablementIfInstalled(tt.pluginName)
			if (err != nil) != tt.wantErr {
				t.Errorf("CheckPluginEnablementIfInstalled() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("CheckPluginEnablementIfInstalled() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestConsolePluginManagerCheckPluginInstallment(t *testing.T) {
	manager := newTestPluginManager()
	tests := []struct {
		name       string
		manager    *ConsolePluginManager
		pluginName string
		want       bool
	}{
		{
			"TestUninstalledConsolePlugin",
			manager,
			"non-installed",
			false,
		},
		{
			"TestInstalledConsolePlugin",
			manager,
			"test-consoleplugin",
			true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := tt.manager.CheckPluginInstallment(tt.pluginName); got != tt.want {
				t.Errorf("CheckPluginInstallment() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestConsolePluginManagerGetConsolePlugin(t *testing.T) {
	manager := newTestPluginManager()

	tests := []struct {
		name       string
		manager    *ConsolePluginManager
		pluginName string
		want       *ConsolePlugin
		wantErr    bool
	}{
		{
			"TestGetUninstalledConsolePlugin",
			manager,
			"non-installed-consoleplugin",
			nil,
			true,
		},
		{
			"TestGetITestPlugin",
			manager,
			"test-consoleplugin",
			&testConsolePlugin,
			false,
		},
		{
			"TestGetDummyPlugin",
			manager,
			"dummy-consoleplugin",
			&dummyConsolePlugin,
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := manager.GetConsolePlugin(tt.pluginName)
			if (err != nil) != tt.wantErr {
				t.Errorf("GetConsolePlugin() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("GetConsolePlugin() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestConsolePluginManagerListConsolePlugins(t *testing.T) {
	manager := newTestPluginManager()
	tests := []struct {
		name    string
		want    []ConsolePlugin
		wantErr bool
	}{
		{
			"TestListConsolePlugins",
			[]ConsolePlugin{dummyConsolePlugin, testConsolePlugin}, // alphabetical order
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := manager.ListConsolePlugins()
			if (err != nil) != tt.wantErr {
				t.Errorf("ListConsolePlugins() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("ListConsolePlugins() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestConsolePluginManagerSetPluginEnablementIfInstalled(t *testing.T) {
	manager := newTestPluginManager()
	tests := []struct {
		name       string
		manager    *ConsolePluginManager
		pluginName string
		newEnabled bool
		wantErr    bool
	}{
		{
			"TestSetUninstalledConsolePlugin",
			newTestPluginManager(),
			"non-installed-consoleplugin",
			false,
			true,
		},
		{
			"TestSetInstalledConsolePluginSkip",
			newTestPluginManager(),
			"test-consoleplugin",
			true,
			false,
		},
		{
			"TestSetInstalledConsolePluginSkip",
			newTestPluginManager(),
			"test-consoleplugin",
			false,
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := manager.SetPluginEnablementIfInstalled(tt.pluginName, tt.newEnabled); (err != nil) != tt.wantErr {
				t.Errorf("SetPluginEnablementIfInstalled() error = %v, wantErr %v", err, tt.wantErr)
			} else {
				cpEnabled, _ := manager.CheckPluginEnablementIfInstalled(tt.pluginName)
				if cpEnabled != tt.newEnabled {
					t.Errorf("SetPluginEnablementIfInstalled() result enabled = %v, wantEnabled %v", cpEnabled, tt.newEnabled)
				}
			}
		})
	}
}

func TestGetConsolePlugin(t *testing.T) {
	client := newFakeDynamicClientSet()
	tests := []struct {
		name       string
		pluginName string
		want       *ConsolePlugin
		wantErr    bool
	}{
		{
			"GetNonExistingConsolePlugin",
			"non-existing-consoleplugin",
			nil,
			true,
		},
		{
			"GetExistingConsolePlugin",
			"test-consoleplugin",
			&testConsolePlugin,
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := GetConsolePlugin(client, tt.pluginName)
			if (err != nil) != tt.wantErr {
				t.Errorf("GetConsolePlugin() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("GetConsolePlugin() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestListConsolePlugins(t *testing.T) {
	client := newFakeDynamicClientSet()
	tests := []struct {
		name    string
		want    []ConsolePlugin
		wantErr bool
	}{
		{
			"TestListConsolePlugins",
			[]ConsolePlugin{dummyConsolePlugin, testConsolePlugin}, // alphabetical order
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := ListConsolePlugins(client)
			if (err != nil) != tt.wantErr {
				t.Errorf("ListConsolePlugins() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("ListConsolePlugins() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestNewConsolePluginManager(t *testing.T) {
	tests := []struct {
		name    string
		config  *rest.Config
		wantErr bool
	}{
		{
			"TestNewManager",
			&rest.Config{},
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := NewConsolePluginManager(tt.config)
			if (err != nil) != tt.wantErr {
				t.Errorf("NewConsolePluginManager() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
		})
	}
}

func TestPatchConsolePlugin(t *testing.T) {
	tests := []struct {
		name       string
		client     dynamic.Interface
		pluginName string
		enabled    bool
		wantErr    bool
	}{
		{
			"TestPathNonExistingConsolePlugin",
			newFakeDynamicClientSet(),
			"non-existing-consoleplugin",
			false,
			true,
		},
		{
			"TestPathExistingConsolePlugin",
			newFakeDynamicClientSet(),
			"test-consoleplugin",
			false,
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			data := []byte(fmt.Sprintf(`{"spec": {"enabled": %t}}`, tt.enabled))
			err := PatchConsolePlugin(tt.client, tt.pluginName, data)
			if (err != nil) != tt.wantErr {
				t.Errorf("PatchConsolePlugin() error = %v, wantErr %v", err, tt.wantErr)
			}
			if err == nil {
				if cp, _ := GetConsolePlugin(tt.client, tt.pluginName); cp.Spec.Enabled != tt.enabled {
					t.Errorf("PatchConsolePlugin() enabled = %v,  wanted %v", cp.Spec.Enabled, tt.enabled)
				}
			}
		})
	}
}
